# hemicube.py
#
# Functions to deal with hemicubes

from gl_functions import *

import math
import struct

def createHemicubeDeltaFormFactors(resolution):
    delta_ffs = []
    width = 2*resolution
    height = width
    R = 2.0*resolution
    for x in xrange(width):
        delta_ffs_col = []
        for y in xrange(height):
            tw = -resolution + x
            th = -resolution + y
            cw = math.cos(math.pi*tw/R)
            ch = math.cos(math.pi*th/R)
            delta_ff = cw*ch
            delta_ffs_col.append(delta_ff)
        delta_ffs.append(delta_ffs_col)
    return delta_ffs

def hemicubeFormFactorsForPatchScene(delta_formfactors, resolution, patch_scene):
    width = resolution*3
    height = resolution*3
    form_factors = {}
    start = resolution/2
    end = start + 2*resolution
    for x in xrange(start, end):
        for y in xrange(start, end):
            # find a patch and give it its ff coefficient
            # patch_scene should be the hemicube around a patch
            # each pixel in scene is an rgb value, and 
            # the rgb values map to an index
            index = 3*(x*width + y)
            patch_scene_str = patch_scene[index] + patch_scene[index + 1] + patch_scene[index + 2]
            rgb = struct.unpack('>3B', patch_scene_str)
            r = rgb[0]
            g = rgb[1]<<8
            b = rgb[2]<<16
            patch_index = r + g + b
            form_factor = delta_formfactors[x - start][y - start]
            try:
                form_factors[patch_index] += form_factor
            except KeyError:
                form_factors[patch_index] = form_factor

    # need to correct the form factors by A, where A is the hemicube's surface area
    S = resolution*resolution + 4.0*resolution*(resolution/2.0)
    A = 2.0/S
    for ff_key in form_factors:
        form_factors[ff_key] *= A
        
    return form_factors

def hemicubeFormFactorForPatchj(delta_formfactors, resolution, patch_scene, patch_j_index):
    # similar to hemicubeFormFactorsForPatchScene, except we are only trying to compute 1
    # form factor, not all of them
    width = resolution*3
    height = resolution*3
    form_factor_j = 0
    start = resolution/2
    end = start + 2*resolution
    for x in xrange(start, end):
        for y in xrange(start, end):
            # find a patch and give it its ff coefficient
            # patch_scene should be the hemicube around a patch
            # each pixel in scene is an rgb value, and 
            # the rgb values map to an index
            index = 3*(x*width + y)
            patch_scene_str = patch_scene[index] + patch_scene[index + 1] + patch_scene[index + 2]
            rgb = struct.unpack('>3B', patch_scene_str)
            r = rgb[0]
            g = rgb[1]<<8
            b = rgb[2]<<16
            patch_index = r + g + b
            if patch_index == patch_j_index:
                form_factor_j += delta_formfactors[x - start][y - start]

    # need to correct the form factor by A, where A is the hemicube's surface area
    S = resolution*resolution + 4.0*resolution*(resolution/2.0)
    A = 2.0/S
    form_factor_j *= A
        
    return form_factor_j

def glRenderHemicubeViewport(x, y, resolution, eye, center, up, patches, display_list=None, arrays=None):
    glViewport(x, y, resolution, resolution)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(90, 1.0, 1e-3, 50)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], up[0], up[1], up[2])

    # render all the patches
    if display_list is not None:
        glCallList(display_list)
    elif arrays is not None:
        num_patches = len(patches)
        drawGlArraysForPatches(arrays, num_patches)
    else:
        createIndexedGL_TRIANGLESForPatches(patches)

def glRenderHemicubeForPatch(patch, patches, resolution, display_function=glutDisplay, display_list=None, arrays=None):
    # use gl to render a hemicube around the patch

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
    # compute the patch's camera direction
    c = triangleCentre(patch.triangle)
    norm = triangleNormal(patch.triangle)
    inv_norm = [-norm[0], -norm[1], -norm[2]]
    side = crossProduct3d(norm, [1.0, 2.0, 3.0])
    if magnitude(side) == 0.0:
        side = crossProduct3d(norm, [1.0, 1.0, 1.0])
    inv_side = [-side[0], -side[1], -side[2]]
    
    vct_front = crossProduct3d(side, norm)
    vct_back = crossProduct3d(inv_side, norm)
    vct_left = inv_side
    vct_right = side
    
    top = [c[0] + norm[0], c[1] + norm[1], c[2] + norm[2]]
    front = [c[0] + vct_front[0], c[1] + vct_front[1], c[2] + vct_front[2]]
    back = [c[0] + vct_back[0], c[1] + vct_back[1], c[2] + vct_back[2]]
    left = [c[0] + vct_left[0], c[1] + vct_left[1], c[2] + vct_left[2]]
    right = [c[0] + vct_right[0], c[1] + vct_right[1], c[2] + vct_right[2]]

    # render the scene
    # top
    glRenderHemicubeViewport(resolution, resolution, resolution, c, top, vct_front, patches, display_list, arrays)

    # front
    glRenderHemicubeViewport(resolution, 2*resolution, resolution, c, front, inv_norm, patches, display_list, arrays)

    # back
    glRenderHemicubeViewport(resolution, 0, resolution, c, back, norm, patches, display_list, arrays)

    # left
    glRenderHemicubeViewport(0, resolution, resolution, c, left, vct_front, patches, display_list, arrays)

    # right
    glRenderHemicubeViewport(2*resolution, resolution, resolution, c, right, vct_front, patches, display_list, arrays)

    # display the scene
    display_function()

def computeHemicubeGLFormFactorsForPatch(patch, patches, delta_formfactors, resolution=256, display_list=None, arrays=None):
    glRenderHemicubeForPatch(patch, patches, resolution, glutDisplay, display_list, arrays)
    edge_length = 3*resolution
    patch_scene = glReadPixels(0, 0, edge_length, edge_length, GL_RGB, GL_UNSIGNED_BYTE)
    return hemicubeFormFactorsForPatchScene(delta_formfactors, resolution, patch_scene)

def computeHemicubeGLFormFactorForPatchiToPatchj(patch_i, patch_j, patches, delta_formfactors, resolution=256, display_list=None, arrays=None):
    glRenderHemicubeForPatch(patch_i, patches, resolution, glutDisplay, display_list, arrays)
    edge_length = 3*resolution
    patch_scene = glReadPixels(0, 0, edge_length, edge_length, GL_RGB, GL_UNSIGNED_BYTE)
    return hemicubeFormFactorForPatchj(delta_formfactors, resolution, patch_scene, patches.index(patch_j))

def createHemicubeGLUTRenderingContext(resolution):
    width = 3*resolution
    height = width
    createGLUTRenderingContext(width, height)

